package com.sciology.xpathutility.view;

import java.io.StringReader;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.contentassist.ComboContentAssistSubjectAdapter;
import org.eclipse.jface.contentassist.SubjectControlContentAssistant;
import org.eclipse.jface.fieldassist.ComboContentAdapter;
import org.eclipse.jface.fieldassist.ContentProposalAdapter;
import org.eclipse.jface.fieldassist.SimpleContentProposalProvider;
import org.eclipse.jface.text.DefaultInformationControl;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IInformationControl;
import org.eclipse.jface.text.IInformationControlCreator;
import org.eclipse.jface.text.TextViewerUndoManager;
import org.eclipse.jface.text.contentassist.IContentAssistant;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.jface.text.source.SourceViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.TabFolder;
import org.eclipse.swt.widgets.TabItem;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IPropertyListener;
import org.eclipse.ui.actions.ActionFactory;
import org.eclipse.ui.commands.AbstractHandler;
import org.eclipse.ui.commands.ExecutionException;
import org.eclipse.ui.commands.IHandler;
import org.eclipse.ui.contentassist.ContentAssistHandler;
import org.eclipse.ui.part.ViewPart;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXParseException;

import com.sciology.xpathutility.XPathWorkbookActivator;
import com.sciology.xpathutility.contentassist.XPathContentAssistProcessor;
import com.sciology.xpathutility.utils.XMLSrcViewerConfiguration;


public class XPathWorkbookView extends ViewPart {

	private String text="";
	private IDocument sourceXMLDocument;
	private SourceViewer sourceXMLViewer, xpathResultViewer;
	private TabFolder xpathResultFolder;
	private TabFolder sourceXMLFolder;
	private TabItem xPathResultTabItem;
	private TabItem sourceXMLTabItem;
	private org.w3c.dom.Document headerDocment;
	private IAction undoAction, redoAction, cutAction, copyAction, pasteAction;
	private IAction clearAllAction, selectAllAction;
	private TextViewerUndoManager defaultUndoManager;
	private Combo xPathHistoryCombo;	
	private Button xPathButton;
	private SashForm sashForm;
	private Label xPathErrorLabel;
	private Text xPathErrorText;
	private String responseXMLString;
	private ContentProposalAdapter adapter = null;
	private String[] EMPTY_PROPOSAL_ARRAY = {"no proposal"};

	// XPath Content Assist
	private SubjectControlContentAssistant xPathContentAssistant;

	  
	public XPathWorkbookView() {
	}
	
	public void createPartControl(final Composite parent) {
		parent.setLayout(new GridLayout(1, false));
		parent.setLayoutData(new GridData(GridData.FILL_BOTH));

		// server mode composite 
		Composite groupComposite = new Composite(parent, SWT.NONE);
		GridLayout groupLayout = new GridLayout();
		groupLayout.horizontalSpacing = 2;
		groupLayout.marginHeight = 2;
		groupLayout.marginWidth = 2;
		groupLayout.verticalSpacing = 2;
		groupComposite.setLayout(groupLayout);
		
		GridData data = new GridData(GridData.FILL_HORIZONTAL |GridData.HORIZONTAL_ALIGN_BEGINNING | GridData.VERTICAL_ALIGN_BEGINNING);
		groupComposite.setLayoutData(data);		

		Group xpathUtilityGroup = new Group(groupComposite, SWT.SHADOW_ETCHED_IN);
		xpathUtilityGroup.setText("  XPath Utility  ");
		xpathUtilityGroup.setLayout(groupLayout);
		xpathUtilityGroup.setLayoutData(data);
		
		// create a Label and non-editable Text Field Control. This is used to catch the XPath error to the user.
		createErrorTextControl(xpathUtilityGroup);

		// create a Label and Text field to enter the XPath expression and execute
		createXPathTextControl(xpathUtilityGroup);
		
		// XPath Content Assist
		// If the content assist need to be disabled, then do comment the following two lines of code.
		// Also do comment the "createXPathDynamicContentAssist(xPathHistoryCmbo.getText());" in the 
		// method addListenersToXPathtextControls();
	    createStaticXPathContentAssist();
		createDynamicXPathContentAssist();
	    
		// Add listeners to the XPath Expression Text field and Execute button
		addListenersToXPathComboControl();
		
		sashForm = new SashForm(parent, SWT.HORIZONTAL);
		sashForm.setLayout(new FillLayout());
		sashForm.setLayoutData(new GridData(GridData.FILL_BOTH));

		sourceXMLFolder = new TabFolder(sashForm,SWT.NONE);
		sourceXMLTabItem = new TabItem(sourceXMLFolder,SWT.NONE);
		sourceXMLTabItem.setText("Source XML");

		// create the RequestViewer with the SourceView
		createSrcXMLViewerControl();
		
		xpathResultFolder = new TabFolder(sashForm,SWT.NONE);
		xPathResultTabItem = new TabItem(xpathResultFolder,SWT.NONE);
		xPathResultTabItem.setText("XPath Result");	
		xpathResultViewer = new SourceViewer(xpathResultFolder, null,SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL);
		xpathResultViewer.setInput(this);
		xpathResultViewer.configure(new XMLSrcViewerConfiguration());
		xPathResultTabItem.setControl(xpathResultViewer.getControl());

		createEditActionsAndInitialize();
		 
		if (text != null &&(!text.equals(""))){
			refreshData();
		}

		clearAllAction = new Action("Clear All"){
			public void run(){
				sourceXMLViewer.setDocument(sourceXMLDocument);
				sourceXMLDocument.set("");
				sourceXMLViewer.refresh();
				xpathResultViewer.setDocument(new Document(""));
				xpathResultViewer.refresh();
				xPathHistoryCombo.removeAll();
				responseXMLString = "";
			}
		};
		clearAllAction.setImageDescriptor(XPathWorkbookActivator.imageDescriptorFromPlugin(
				XPathWorkbookActivator.getDefault().getBundle().getSymbolicName(), "/icons/clear_all.gif"));

		createContextMenu();
		createToolbar();
	}

	//Dynamic XPath Content Assist
	private void createXPathDynamicContentAssist(String xPathExpr) {
		NodeList nodeList = null;
		NodeList childNodes = null;
		Set<String> proposals = new TreeSet<String>();
		
		try {
			if ( responseXMLString != null && xPathExpr != null ) {
				nodeList = XMLUtilities.evaluateXPathDOM(new StringReader(responseXMLString), xPathExpr);
				if (nodeList  != null) {
					int nodeCount = nodeList.getLength();
					while (nodeCount > 0 ) {
						Node item = nodeList.item(nodeCount - 1);
						childNodes = item.getChildNodes();
						int childrenCount = childNodes.getLength();
						while (childrenCount > 0) {
							Node childItem = childNodes.item(childrenCount - 1);
							if (childItem.getLocalName() != null && !(childItem.getNodeName().equalsIgnoreCase("#text"))) {
								proposals.add(childItem.getNodeName());
							}
							childrenCount--;
						}
						nodeCount--;
					}
				}
				int proposalSize = proposals.size();
				if ( proposalSize > 0 && nodeList != null ) {
					String[] proposalString = new String[proposalSize];
					Iterator proposalIter = proposals.iterator();
					int i = 0;
					while (proposalIter.hasNext()) {
						proposalString[i] = (String) proposalIter.next();
						i++;
					}
					adapter.setContentProposalProvider(new SimpleContentProposalProvider(proposalString));
				} else {
					adapter.setContentProposalProvider(new SimpleContentProposalProvider(EMPTY_PROPOSAL_ARRAY));
				}
			}
		} catch (Exception e) {
			// No need to do anything as this XPath is not correct. No need to Error out.
			// XPathUtilLog.logError(XPathWorkbookActivator.getDefault(), e.toString());
		}
	}
	
	
	/**
	 * XPath Content Assist
	 * Create the Content Assist statically from a xml file. All the proposals are read from
	 * the file "xalanCompletions.xml". All the functions and expressions are described in this
	 * xml file.
	 */
	private void createStaticXPathContentAssist() {
	    xPathContentAssistant = new SubjectControlContentAssistant();
	    xPathContentAssistant.enableAutoActivation(false);
	    xPathContentAssistant.enableAutoInsert(true);
	    xPathContentAssistant.setContentAssistProcessor(new XPathContentAssistProcessor(), IDocument.DEFAULT_CONTENT_TYPE);
	    xPathContentAssistant.setContextInformationPopupOrientation(IContentAssistant.CONTEXT_INFO_ABOVE);
	    xPathContentAssistant.setRestoreCompletionProposalSize(XPathWorkbookActivator.getDefault().getDialogSettings());
	    xPathContentAssistant.setInformationControlCreator(new IInformationControlCreator() {
	      public IInformationControl createInformationControl(Shell parent) {
	        return new DefaultInformationControl(parent);
	      }
	    });
	    xPathContentAssistant.install(new ComboContentAssistSubjectAdapter(xPathHistoryCombo));
	    IHandler handler = new AbstractHandler() {
	      public Object execute(Map parameterValuesByName) throws ExecutionException {
	        xPathContentAssistant.showPossibleCompletions();
	        return null;
	      }
	    };
	    ContentAssistHandler.createHandlerForCombo(xPathHistoryCombo, xPathContentAssistant);
	}

	/**
	 * XPath Content Assist
	 * Create the Content Assist which directly picks up the DOM of the
	 * result from the operation execution. Currently the activation is enabled only by "/".
	 * 
	 * This features is available from the eclipse 3.3 and greater.
	 * NOTE : In the initial stage we just send the EMPTY_PROPOSAL_ARRAY. Whenever the 
	 * content in the XPath Combo changes the proposals are set dynamically in the listener. 
	 */
    private void createDynamicXPathContentAssist() {
	    adapter = new ContentProposalAdapter(
	    		xPathHistoryCombo, 
	    		new ComboContentAdapter(), 
	    		new SimpleContentProposalProvider(EMPTY_PROPOSAL_ARRAY),
	    		null, 
	    		new char[] {'/'});
	    adapter.setProposalAcceptanceStyle(ContentProposalAdapter.PROPOSAL_INSERT);
	    adapter.setFilterStyle(ContentProposalAdapter.FILTER_NONE);
	}

	private void createEditActionsAndInitialize() {
		undoAction = new Action("&Undo") {
			public void run() {
				defaultUndoManager.undo();	
			}
		};
		redoAction = new Action("&Redo") {
			public void run() {
				defaultUndoManager.redo();	
			}
		};
		selectAllAction = new Action("Select All") {
			public void run(){
				if (sourceXMLViewer.getControl().isFocusControl()) {
					sourceXMLViewer.getTextWidget().selectAll();
				} else if (xpathResultViewer.getControl().isFocusControl()) {
					xpathResultViewer.getTextWidget().selectAll();
				}
			}
		};
		cutAction = new Action("Cu&t") {
			public void run() {
				if (sourceXMLViewer.getControl().isFocusControl()) {
					sourceXMLViewer.getTextWidget().cut();
					sourceXMLDocument.set(sourceXMLViewer.getTextWidget().getText());
					sourceXMLViewer.setDocument(sourceXMLDocument);
				}
			}
		};
		copyAction = new Action("&Copy") {
			public void run() {
				if (sourceXMLViewer.getControl().isFocusControl()) {
					sourceXMLViewer.getTextWidget().copy();
				} else if (xpathResultViewer.getControl().isFocusControl()) {
					xpathResultViewer.getTextWidget().selectAll();
					xpathResultViewer.getTextWidget().copy();
				}
			}
		};
		pasteAction = new Action("&Paste"){
			public void run(){
				if (sourceXMLViewer.getControl().isFocusControl()) {
					sourceXMLViewer.getTextWidget().paste();
				}
				sourceXMLDocument.set(sourceXMLViewer.getTextWidget().getText());
				sourceXMLViewer.setDocument(sourceXMLDocument);
				responseXMLString = XMLUtilities.convertXMLtoString(XMLUtilities.convertTextoXML(sourceXMLDocument.get()));
				int format = ISourceViewer.FORMAT;
				sourceXMLViewer.doOperation(format);

			}
		};

		IActionBars actionBar = getViewSite().getActionBars();
		actionBar.setGlobalActionHandler(ActionFactory.UNDO.getId(), undoAction);
		actionBar.setGlobalActionHandler(ActionFactory.REDO.getId(), redoAction);
		actionBar.setGlobalActionHandler(ActionFactory.SELECT_ALL.getId(), selectAllAction);
		actionBar.setGlobalActionHandler(ActionFactory.CUT.getId(), cutAction);
		actionBar.setGlobalActionHandler(ActionFactory.COPY.getId(), copyAction);
		actionBar.setGlobalActionHandler(ActionFactory.PASTE.getId(), pasteAction);
	}

	private void createSrcXMLViewerControl() {
		sourceXMLViewer = new SourceViewer(sourceXMLFolder, null,SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL);
		sourceXMLTabItem.setControl(sourceXMLViewer.getControl());
		sourceXMLViewer.setInput(this);
		XMLSrcViewerConfiguration sourceViewerConfiguration = new XMLSrcViewerConfiguration(sourceXMLDocument);
		sourceXMLViewer.configure(sourceViewerConfiguration);

		sourceXMLDocument = new Document();
		sourceXMLViewer.setDocument(sourceXMLDocument);
		sourceXMLDocument.set(text);

		defaultUndoManager = new TextViewerUndoManager(50);
		sourceXMLViewer.setUndoManager(defaultUndoManager);
		defaultUndoManager.connect(sourceXMLViewer);
	}

	private void createXPathTextControl(final Composite parent) {
		Composite labelTextComposite = new Composite(parent, SWT.NONE);
		GridData labelTextGD = new GridData(SWT.FILL, SWT.CENTER, true, false);
		labelTextGD.widthHint = 300;
		labelTextGD.horizontalSpan = 3;
		labelTextComposite.setLayoutData(labelTextGD);
		GridLayout labelTextGL = new GridLayout(3, false);
		labelTextGL.horizontalSpacing = 3;
		labelTextGL.verticalSpacing = 0;
		labelTextGL.marginHeight = 0;
		labelTextComposite.setLayout(labelTextGL);
		
		Label xPathLabel = new Label(labelTextComposite, SWT.NONE);
		xPathLabel.setText("Enter XPath :");
		
		xPathHistoryCombo = new Combo(labelTextComposite, SWT.DROP_DOWN | SWT.SINGLE | SWT.LEAD);
		xPathHistoryCombo.setFocus();
		xPathHistoryCombo.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
		xPathHistoryCombo.setVisibleItemCount(5);
		xPathHistoryCombo.setToolTipText("Enter XPath for Source XML");

		xPathButton = new Button(labelTextComposite, SWT.FLAT);
		xPathButton.setText("Execute");
		xPathButton.setToolTipText("Execute XPath on the Source XML");
		xPathButton.setEnabled(false);
	}

	private void createErrorTextControl(final Composite parent) {
		Composite errorTextComposite = new Composite(parent, SWT.NONE);
		GridData errorTextGD = new GridData(SWT.FILL, SWT.CENTER, true, false);
		errorTextGD.widthHint = 300;
		errorTextGD.horizontalSpan = 3;
		errorTextComposite.setLayoutData(errorTextGD);
		GridLayout errorTextGL = new GridLayout(3, false);
		errorTextGL.horizontalSpacing = 0;
		errorTextGL.verticalSpacing = 0;
		errorTextGL.marginHeight = 0;
		errorTextComposite.setLayout(errorTextGL);
		
		xPathErrorLabel = new Label(errorTextComposite, SWT.NONE);
		xPathErrorLabel.setText("XPath Error : ");
		xPathErrorLabel.setVisible(false);
		
		xPathErrorText = new Text(errorTextComposite, SWT.NONE);
		xPathErrorText.setEditable(false);
		xPathErrorText.setVisible(false);
		xPathErrorText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
		xPathErrorText.setForeground(new Color(errorTextComposite.getDisplay(), 255, 0 , 0));
	}
	
	private void addListenersToXPathComboControl() {
		xPathButton.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				super.widgetSelected(e);
				executeXPath4Results();
			}
		});
		
		xPathHistoryCombo.addSelectionListener(new SelectionAdapter() {
			public void widgetDefaultSelected(SelectionEvent e) {
				super.widgetDefaultSelected(e);
        		prepare4XPathExecution();
			}
			
			public void widgetSelected(SelectionEvent e) {
				super.widgetSelected(e);
        		prepare4XPathExecution();
			}
		});
		
		xPathHistoryCombo.addModifyListener(new ModifyListener(){
        	public void modifyText(ModifyEvent e) {
        		boolean isXPathValid = false;
        		if (!(xPathHistoryCombo.getText().trim().equals(""))){
            		createXPathDynamicContentAssist(xPathHistoryCombo.getText());
            		try {
                		isXPathValid = XMLUtilities.isValidXPathExpression(xPathHistoryCombo.getText());
            			xPathErrorText.setVisible(false);
            			xPathButton.setEnabled(true);
            		}
            		catch (Exception ex) {
            			isXPathValid = false;
            			xPathErrorText.setVisible(true);
            			xPathErrorText.setText(ex.getMessage());
            			xPathButton.setEnabled(false);
            		}
        		}
        	}
        });
		
	}

	
	private void createContextMenu() {
        // Create menu manager.
     MenuManager menuMgr = new MenuManager();
        menuMgr.setRemoveAllWhenShown(true);
        menuMgr.addMenuListener(new IMenuListener() {
                public void menuAboutToShow(IMenuManager mgr) {
                	fillContextMenu(mgr);
                }
        });
        Menu menu = menuMgr.createContextMenu(sourceXMLViewer.getControl());
        sourceXMLViewer.getControl().setMenu(menu);
        xpathResultViewer.getControl().setMenu(menu);
        getSite().registerContextMenu(menuMgr, sourceXMLViewer);
        
	}
	
	private void fillContextMenu(IMenuManager mgr) {
		mgr.add(cutAction);
		mgr.add(copyAction);
		mgr.add(pasteAction);
		mgr.add(new Separator());
		mgr.add(selectAllAction);
   	}

	
	private void createToolbar() {
		IToolBarManager mgr = getViewSite().getActionBars().getToolBarManager();
		mgr.add(clearAllAction);
	}

	public void refreshData() {
		clearAll();
		int format = ISourceViewer.FORMAT;
		sourceXMLViewer.doOperation(format);
	}

	public void clearAll() {
		sourceXMLViewer.setInput(null);
		sourceXMLViewer.refresh();
		xpathResultViewer.setInput(null);
		xpathResultViewer.refresh();
	}

	public void setFocus() {
	}

	public void addPropertyListener(IPropertyListener l) {
		super.addPropertyListener(l);
	}

	//////
	// helper methods for the XPath Result Source View
	//////
	private org.w3c.dom.Document addResultNodeListToResult(org.w3c.dom.NodeList nodeList){
		headerDocment = XMLUtilities.createDocument();
		Element root = headerDocment.createElement("XPath_Result");
		headerDocment.appendChild(root);
		for (int i=0; i<nodeList.getLength(); i++) {
			Node childNode = headerDocment.importNode(nodeList.item(i), true);
			root.appendChild(childNode);
		}
		return headerDocment;
	}

	private void populateXPathResponse(final org.w3c.dom.Document dom) {
		Display.getDefault().syncExec(
				new Runnable() {
				public void run () {
			IDocument responseDocument = new Document();
			xpathResultViewer.setDocument(responseDocument);
			responseDocument.set(XMLUtilities.convertXMLtoString(dom));
			int format = ISourceViewer.FORMAT;
			xpathResultViewer.doOperation(format);
		}
		});
	}

	private void updateXPathResponseTab(final org.w3c.dom.NodeList nodeList) {
		Display.getDefault().syncExec(new Runnable() {
			public void run () {
				populateXPathResponse(addResultNodeListToResult(nodeList));
				xpathResultViewer.refresh();
			}
		});
	}

	private void executeXPath4Results() {
		NodeList nodeList = null;
		xPathHistoryCombo.add(xPathHistoryCombo.getText(), xPathHistoryCombo.getItemCount());
		xpathResultFolder.setSelection(xPathResultTabItem);
		try {
			nodeList = XMLUtilities.evaluateXPathDOM(new StringReader(responseXMLString), xPathHistoryCombo.getText());
		}
		catch (SAXParseException ex) {
			System.out.println("Exception in XPath Eval :" + ex.toString());
			StringBuffer sb = new StringBuffer();
			sb.append("\n" + ex.getMessage() + "\n");
			sb.append("Error at line number : " + ex.getLineNumber() + " and ");
			sb.append("Column number : " + ex.getColumnNumber() + "\n");
			nodeList = XMLUtilities.defaultErrorXML(sb.toString());
		}
		catch (Exception ex) {
			System.out.println("Exception in XPath Eval :" + ex.toString());
			nodeList = XMLUtilities.defaultErrorXML(ex.toString());
		}
		updateXPathResponseTab(nodeList);
	}

	private void prepare4XPathExecution() {
		boolean isXPathValid = false;
		if (!(xPathHistoryCombo.getText().trim().equals(""))){
			try {
				isXPathValid = XMLUtilities.isValidXPathExpression(xPathHistoryCombo.getText());
				xPathErrorText.setVisible(false);
				xPathButton.setEnabled(true);
				executeXPath4Results();
			}
			catch (Exception ex) {
				isXPathValid = false;
				xPathErrorText.setVisible(true);
				xPathErrorText.setText(ex.getMessage());
				xPathButton.setEnabled(false);
			}
		}
	}

}
